package com.hxl.alert.scheduler;

import cn.hutool.core.util.ObjectUtil;
import com.hxl.alert.constant.CacheConstants;
import com.hxl.alert.entity.AlertOriginBase;
import com.hxl.alert.service.RocketMQAdminService;
import com.hxl.alert.utils.AlertTimeUtils;
import com.hxl.alert.utils.RedisUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.text.ParseException;
import java.util.Date;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Component
public class AlertScheduler {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Resource
    private RocketMQAdminService rocketMQAdminService;

    @Resource
    private RedisUtils redisUtils;

    @Value("${mq_unconsumed_message.tolerate_max_value}")
    private long tolerateMaxValue;

    /**
     * 定时任务入口方法（每5秒执行一次）
     * 负责处理两类时间窗口：
     * 1. 已经到期的窗口（必须立即处理）
     * 2. 未来2分钟内将到期的窗口（提前检查优化）
     */
    @Scheduled(fixedRate = 5000,initialDelay = 10000)
    public void processTimeWindows() {
        if (ObjectUtil.isNull(redisUtils.get(CacheConstants.UNCONSUMED_KEY)))
            return;
        logger.info("任务调度开始:{}", AlertTimeUtils.getSdf().format(new Date()));
        // 尝试获取分布式锁（30秒自动过期）
        Boolean locked = redisUtils.setIfAbsent(CacheConstants.SCHEDULER_LOCK, "1", 30, TimeUnit.SECONDS);
        // 未获取到锁则直接返回
        if (!Boolean.TRUE.equals(locked)) return;

        try {
            // 处理已到期的检查项
            processExpiredWindows();
            // 处理未来2分钟内的检查项（积压消息优化）
            processFutureWindows();
        } finally {
            // 无论成功失败都必须释放锁
            redisUtils.delete(CacheConstants.SCHEDULER_LOCK);
        }
    }

    @Scheduled(fixedRate = 10000, initialDelay = 5000) //延迟 5 秒首次执行
    public void queryUnConsumedMessageCount() {
//        logger.info("积压消息查询任务调度开始:{}", AlertTimeUtils.getSdf().format(new Date()));
        if (ObjectUtil.isNotNull(redisUtils.get(CacheConstants.UNCONSUMED_KEY)))
            return;
        try {
            long l = rocketMQAdminService.queryUnConsumedMessageCount();
            if (l > tolerateMaxValue) return;
            redisUtils.set(CacheConstants.UNCONSUMED_KEY, 1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 处理已过期的时间窗口（当前时间之前的检查项）
     */
    private void processExpiredWindows() {
        // 获取当前时间戳
        long now = System.currentTimeMillis();

        // 查询所有score（检查时间）<=当前时间的待处理项
        Set<String> expiredIds = redisUtils.zRangeByScore(CacheConstants.PENDING_CHECKS, 0, now)
                .stream()
                .map(String::valueOf)
                .collect(Collectors.toSet());
        // 遍历处理每个过期项
        expiredIds.forEach(this::processSingleWindow);
    }

    /**
     * 处理未来时间窗口（当前时间+2分钟内的检查项）
     * 用于优化积压消息场景，提前完成可能满足条件的检查
     */
    private void processFutureWindows() {
        long now = System.currentTimeMillis();
        // 查询未来2分钟内的检查项
        Set<String> upcomingIds = redisUtils.zRangeByScore(CacheConstants.PENDING_CHECKS, now, now + 120_000)
                .stream()
                .map(String::valueOf)
                .collect(Collectors.toSet());
        // 提前处理可能完成的时间窗口
        upcomingIds.forEach(this::processSingleWindow);
    }

    /**
     * 处理单个故障ID对应的时间窗口
     *
     * @param faultId 故障唯一标识
     */
    private void processSingleWindow(String faultId) {
        // 从Redis获取对应的越限告警
        AlertOriginBase yueXian = (AlertOriginBase) redisUtils.get(CacheConstants.YUEXIAN_KEY + faultId);
        if (yueXian == null) {
            // 无越限告警则直接返回
            return;
        }
        try {
            // 计算时间窗口结束时间（越限时间+3分钟）
            long windowEnd = AlertTimeUtils.toTimestamp(yueXian.getOccurTime()) + 180_000;
            long currentTime = System.currentTimeMillis();

            if (currentTime >= windowEnd) {
                long l = redisUtils.countByScoreRange(CacheConstants.FAULT_KEY, AlertTimeUtils.toTimestamp(yueXian.getOccurTime()), windowEnd);
                // 情况1：窗口已关闭，进行最终处理
                processWindowClosed(yueXian, l);
            } else {
                // 情况2：窗口未关闭，检查是否可能提前完成
                checkEarlyCompletion(faultId, yueXian, currentTime);
            }
        } catch (ParseException e) {
            // 时间格式解析异常处理
            e.printStackTrace();
        }
    }

    /**
     * 处理已关闭的时间窗口（最终处理）
     *
     * @param yueXian 越限告警对象
     */
    private void processWindowClosed(AlertOriginBase yueXian, long faultSize) {
        if (faultSize > 0) {
            // 情况1：存在有效故障告警，触发混合计算
            computeMixed(yueXian);
        } else {
            // 情况2：无有效故障告警，触发独立计算
            computeSingle(yueXian);
        }
        // 必须清理Redis资源
        cleanResources(yueXian.getFaultId());
    }

    /**
     * 检查是否提前满足计算条件（窗口未关闭但已存在故障告警）
     *
     * @param yueXianId 越限告警ID
     * @param yueXian   越限告警对象
     */
    private void checkEarlyCompletion(String yueXianId, AlertOriginBase yueXian, long currentTime) {
        // 获取对应的故障告警
        try {
            long l = redisUtils.countByScoreRange(CacheConstants.FAULT_KEY, AlertTimeUtils.toTimestamp(yueXian.getOccurTime()), currentTime);
            if (l > 0) {
                // 满足条件时提前完成处理
                computeMixed(yueXian);
                cleanResources(yueXianId);
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }

    /**
     * 清理Redis中的相关数据
     *
     * @param yueXianId 要清理的越限告警ID
     */
    private void cleanResources(String yueXianId) {
        // 从待检查队列移除
        redisUtils.zRemove(CacheConstants.PENDING_CHECKS, yueXianId);
    }

    /**
     * 混合计算方法（越限+故障）
     *
     * @param yueXian 越限告警对象
     */
    private void computeMixed(AlertOriginBase yueXian) {
        logger.info("[混合计算] 故障ID:{} 越限时间:{}",
                yueXian.getFaultId(),
                yueXian.getOccurTime());
        //TODO 实际业务逻辑实现位置
    }

    /**
     * 独立计算方法（仅越限）
     *
     * @param yueXian 越限告警对象
     */
    private void computeSingle(AlertOriginBase yueXian) {
        logger.info("[独立计算] 故障ID:{} 越限时间:{}",
                yueXian.getFaultId(),
                yueXian.getOccurTime());
        //TODO 实际业务逻辑实现位置
    }
}